# -*- coding: UTF-8 -*-
"Des utilitaires liés à HTML qui peuvent servir à tous les projets de World Online."

import re, string

# Configuration de la fonction urlize()
LEADING_PUNCTUATION  = ['(', '<', '&lt;']
TRAILING_PUNCTUATION = ['.', ',', ')', '>', '\n', '&gt;']

# une liste des chaînes susceptibles de représenter des points dans les listes à points
DOTS = ['&middot;', '*', '\xe2\x80\xa2', '&#149;', '&bull;', '&#8226;']

UNENCODED_AMPERSANDS_RE = re.compile(r'&(?!(\w+|#\d+);)')
WORD_SPLIT_RE = re.compile(r'(\s+)')
PUNCTUATION_RE = re.compile('^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % \
    ('|'.join([re.escape(p) for p in LEADING_PUNCTUATION]),
    '|'.join([re.escape(p) for p in TRAILING_PUNCTUATION])))
SIMPLE_EMAIL_RE = re.compile(r'^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$')
LINK_TARGET_ATTRIBUTE = re.compile(r'(<a [^>]*?)target=[^\s>]+')
HTML_GUNK = re.compile(r'(?:<br clear="all">|<i><\/i>|<b><\/b>|<em><\/em>|<strong><\/strong>|<\/?smallcaps>|<\/?uppercase>)', re.IGNORECASE)
HARD_CODED_BULLETS = re.compile(r'((?:<p>(?:%s).*?[a-zA-Z].*?</p>\s*)+)' % '|'.join([re.escape(d) for d in DOTS]), re.DOTALL)
TRAILING_EMPTY_CONTENT = re.compile(r'(?:<p>(?:&nbsp;|\s|<br \/>)*?</p>\s*)+\Z')

def escape(html):
    "Renvoie le code HTML spécifié de telle sorte que les esperluettes, guillemets et crochets y soient codés en dur"
    if not isinstance(html, basestring):
        html = str(html)
    return html.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;')

def linebreaks(value):
    "Convertit les sauts de ligne en balises <p> et <br />"
    value = re.sub(r'\r\n|\r|\n', '\n', value) # on normalise les sauts de ligne
    paras = re.split('\n{2,}', value)
    paras = ['<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
    return '\n\n'.join(paras)

def strip_tags(value):
    "Renvoie le code HTML spécifié de telle sorte que toutes les balises en soient retirées"
    return re.sub(r'<[^>]*?>', '', value)

def strip_entities(value):
    "Renvoie le code HTML spécifié de telle sorte que toutes les entités (&truc;) en soient retirées"
    return re.sub(r'&(?:\w+|#\d);', '', value)

def fix_ampersands(value):
    "Renvoie le code HTML spécifié de telle sorte que toutes les esperluettes non-codées en dur y soient codées correctement"
    return UNENCODED_AMPERSANDS_RE.sub('&amp;', value)

def urlize(text, trim_url_limit=None, nofollow=False):
    """
    Convertit toutes les URL se trouvant dans l'argument text en liens cliquables.
    Fonctionne pour les liens du style http://, https:// et 'www.'. La fonction sait
    gérer les URL se terminant par de la ponctuation (un point, une virgule ou une
    parenthèse de fermeture, par exemple) ou commençant par de la ponctuation (une
    parenthèse d'ouverture, par exemple).

    Si trim_url_limit est différent de None, le texte du lien affichera une URL limitée
    à trim_url_limit caractères.

    Si nofollow vaut True, les liens se verront affecter un attribut rel="nofollow".
    """
    trim_url = lambda x, limit=trim_url_limit: limit is not None and (x[:limit] + (len(x) >=limit and '...' or ''))  or x
    words = WORD_SPLIT_RE.split(text)
    nofollow_attr = nofollow and ' rel="nofollow"' or ''
    for i, word in enumerate(words):
        match = PUNCTUATION_RE.match(word)
        if match:
            lead, middle, trail = match.groups()
            if middle.startswith('www.') or ('@' not in middle and not middle.startswith('http://') and \
                    len(middle) > 0 and middle[0] in string.letters + string.digits and \
                    (middle.endswith('.org') or middle.endswith('.net') or middle.endswith('.com'))):
                middle = '<a href="http://%s"%s>%s</a>' % (middle, nofollow_attr, trim_url(middle))
            if middle.startswith('http://') or middle.startswith('https://'):
                middle = '<a href="%s"%s>%s</a>' % (middle, nofollow_attr, trim_url(middle))
            if '@' in middle and not middle.startswith('www.') and not ':' in middle \
                and SIMPLE_EMAIL_RE.match(middle):
                middle = '<a href="mailto:%s">%s</a>' % (middle, middle)
            if lead + middle + trail != word:
                words[i] = lead + middle + trail
    return ''.join(words)

def clean_html(text):
    """
    Nettoie le code HTML spécifié. Plus précisément, cette fonction :
        * Convertit les balises <b> et <i> en balises <strong> et <em>.
        * Code correctement toutes les esperluettes.
        * Retire de toutes les balises <a> les attributs "target".
        * Élimine le code HTML inutile, comme les balises de présentation
          qui s'ouvrent et se referment immédiatement ou les balises <br clear="all">.
        * Convertit les listes à points codées en dur au sein du code HTML en listes non-ordonnées.
        * Retire du code HTML les trucs du style "<p>&nbsp;&nbsp;</p>" mais seulement si
          ces trucs se trouvent au bas d'un texte.
    """
    from django.utils.text import normalize_newlines
    text = normalize_newlines(text)
    text = re.sub(r'<(/?)\s*b\s*>', '<\\1strong>', text)
    text = re.sub(r'<(/?)\s*i\s*>', '<\\1em>', text)
    text = fix_ampersands(text)
    # On retire de toutes les balises <a> les attributs target="".
    text = LINK_TARGET_ATTRIBUTE.sub('\\1', text)
    # On élimine le code HTML stupide, comme <br clear="all">.
    text = HTML_GUNK.sub('', text)
    # On convertit les listes à points codées en dur en listes HTML non-ordonnées.
    def replace_p_tags(match):
        s = match.group().replace('</p>', '</li>')
        for d in DOTS:
            s = s.replace('<p>%s' % d, '<li>')
        return '<ul>\n%s\n</ul>' % s
    text = HARD_CODED_BULLETS.sub(replace_p_tags, text)
    # On retire les trucs du style "<p>&nbsp;&nbsp;</p>" mais seulement si ces trucs se trouvent au bas d'un texte.
    text = TRAILING_EMPTY_CONTENT.sub('', text)
    return text

